package com.zzjson.common.utils;

import lombok.extern.slf4j.Slf4j;

import java.util.Collection;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
import java.util.function.Supplier;

@Slf4j
public class RetryUtils {

    /**
     * 等待
     * @param sleepMillis 重试间隔，ms
     * @throws InterruptedException
     */
    private static void sleep(long sleepMillis) {
        if (sleepMillis > 0) {
            try {
                Thread.sleep(sleepMillis);
            } catch (InterruptedException e) {
                log.error("RetryUtils#sleep", e);
            }
        }
    }

    /**
     * 接口异常重试
     * <blockquote><pre>
     * 1. 接口异常
     * </pre></blockquote>
     * @param retryTime   重试次数
     * @param sleepMillis 重试间隔，ms
     * @param supplier    执行方法
     * @param <T>
     * @return
     */
    public static <T> T get(Integer retryTime, long sleepMillis, Supplier<T> supplier) {
        for (int i = 1; i <= retryTime; i++) {
            try {
                return supplier.get();
            } catch (Throwable e) {
                log.error("RetryUtils#get, ", e);
            }
            sleep(sleepMillis);
        }
        return supplier.get();
    }

    /**
     * 接口异常重试
     * <blockquote><pre>
     * 1. 接口异常
     * </pre></blockquote>
     * @param retryTime   重试次数
     * @param sleepMillis 重试间隔，ms
     * @param consumer    执行方法
     * @param <T>
     * @return
     */
    public static <T> void accept(Integer retryTime, long sleepMillis, Consumer<T> consumer) {
        for (int i = 1; i <= retryTime; i++) {
            try {
                consumer.accept(null);
                return;
            } catch (Throwable e) {
                log.error("RetryUtils#accept, ", e);
            }
            sleep(sleepMillis);
        }
        consumer.accept(null);
    }

    /**
     * 返回数据为 null 重试
     * <blockquote><pre>
     * 1. 接口异常
     * 2. 数据为null
     * </pre></blockquote>
     * @param retryTime   重试次数
     * @param sleepMillis 重试间隔，ms
     * @param supplier    执行方法
     * @param <T>
     * @return
     */
    public static <T> T getIfNull(int retryTime, long sleepMillis, Supplier<T> supplier) {
        for (int i = 1; i <= retryTime; i++) {
            try {
                T data = supplier.get();
                if (data != null) {
                    return data;
                }
            } catch (Throwable e) {
                log.error("RetryUtils#getIfNull, ", e);
            }
            sleep(sleepMillis);
        }
        return supplier.get();
    }

    /**
     * 返回集合为空重试
     * <blockquote><pre>
     * 1. 接口异常
     * 2. 集合为空
     * 3. 输入和输出数量不匹配
     * </pre></blockquote>
     * @param retryTime   重试次数
     * @param sleepMillis 重试间隔，ms
     * @param supplier    执行方法
     * @param <T>
     * @return
     */
    public static <T extends Collection<?>> T getIfEmpty(int retryTime, long sleepMillis, Supplier<T> supplier) {
        for (int i = 1; i <= retryTime; i++) {
            try {
                T data = supplier.get();
                if (data != null && !data.isEmpty()) {
                    return data;
                }
            } catch (Throwable e) {
                log.error("RetryUtils#getIfEmpty, ", e);
            }
            sleep(sleepMillis);
        }
        return supplier.get();
    }

    /**
     * 输出数量与期望不匹配重试
     * <blockquote><pre>
     * 1. 接口异常
     * 2. 集合为空
     * 3. 输入和输出数量不匹配
     * </pre></blockquote>
     * @param retryTime    重试次数
     * @param sleepMillis  重试间隔，ms
     * @param expectedSize 期望输出值
     * @param supplier     执行方法
     * @param <T>
     * @return
     */
    public static <T extends Collection<?>> T getIfNotMatchExpectedSize(int retryTime, long sleepMillis,
                                                                        int expectedSize, Supplier<T> supplier) {
        for (int i = 1; i <= retryTime; i++) {
            try {
                T data = supplier.get();
                if (data != null && !data.isEmpty() && expectedSize == data.size()) {
                    return data;
                }
            } catch (Throwable e) {
                log.error("RetryUtils#getIfNotMatchExpectedSize, ", e);
            }
            sleep(sleepMillis);
        }
        return supplier.get();
    }

    /**
     * 原地重试
     * <blockquote><pre>
     *         Integer totalCount = RetryUtils.mTry(()-> {
     *             if(Math.random() > 0.5) {
     *                 throw new Exception();
     *             }
     *             return 1;
     *         }, 3, 100);
     * </pre></blockquote>
     * @param supplier      获取数据的方法
     * @param retryTimes    总重试次数
     * @param retryWaitTime 失败后等待时间
     * @param <R>
     * @return
     * @throws Exception
     */
    public static <R> R mTry(Supplier<R> supplier, int retryTimes, int retryWaitTime) throws Exception {
        Exception exception = null;

        for (int i = 0; i < retryTimes; i++) {
            try {
                return supplier.get();
            } catch (Exception e) {
                exception = e;
                log.error("mTry captured exception 抓到了异常", e);
            }

            if (retryWaitTime <= 0) {
                continue;
            }

            try {
                Thread.sleep(retryWaitTime);
            } catch (Exception e) {
                log.error("mTry captured exception 抓到了异常", e);
            }
        }

        if (exception != null) {
            throw exception;
        } else {
            throw new RuntimeException(exception);
        }
    }

    /**
     * 原地重试
     * <blockquote><pre>
     *         RetryUtils.mTry(()-> {
     *             if(Math.random() > 0.5) {
     *                 throw new Exception();
     *             }
     *             return 1;
     *         }, 3, 100);
     * </pre></blockquote>
     * @param callable      获取数据的方法
     * @param retryTimes    总重试次数
     * @param retryWaitTime 失败后等待时间
     * @return
     * @throws Exception
     */
    public static void mTry(Callable callable, int retryTimes, int retryWaitTime) throws Exception {
        Exception exception = null;

        for (int i = 0; i < retryTimes; i++) {
            try {
                callable.call();
            } catch (Exception e) {
                exception = e;
                log.error("mTry captured exception 抓到了异常", e);
            }

            if (retryWaitTime <= 0) {
                continue;
            }

            try {
                Thread.sleep(retryWaitTime);
            } catch (Exception e) {
                log.error("mTry captured exception 抓到了异常", e);
            }
        }

        if (exception != null) {
            throw exception;
        } else {
            throw new RuntimeException(exception);
        }
    }
}